#!/bin/perl
##############################################################################################
# Copyright (c) Microsoft
#
# All rights reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
# EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
# TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
#
# See the Apache Version 2.0 License for specific language governing permissions and
# limitations under the License.
##############################################################################################

#usage:
# perl run_test.pl <Test root dir> <test file>
# test root dir: base directory to look for tests from.
# test file: test.cpp to run

use Cwd;
use Cwd 'abs_path';
use File::Basename;
use File::Find;
use Safe;
use strict;

my $cflag_define = '"-D%s=%s"'; # to be used as sprintf($cflag_define, "NAME", "VALUE");
mkdir("conformance-temp");
my $tmpdir = abs_path('./conformance-temp');
my $test_exec = abs_path("$tmpdir/test.out");

my $tests_root = @ARGV[0];
my $test = abs_path(@ARGV[1]);
if (!$tests_root) {
    print "ERROR: Test root dir not provided\n";
    exit(1);
}
$tests_root = abs_path($tests_root);
chdir($tests_root);


my $CLANG_AMP_HOME="@PROJECT_SOURCE_DIR@";
my $CLANG_AMP_BUILD_DIR="@PROJECT_BINARY_DIR@";

my $AMPTESTLIB="@AMPTESTLIB@";
my $AMPTESTINC="@AMPTESTINC@";
my $RUNTESTSDIR="@RUNTESTSDIR@";

my $CLANG_AMP="$CLANG_AMP_BUILD_DIR/compiler/bin/clang++";
my $CLAMP_CONFIG=`find $CLANG_AMP_BUILD_DIR/bin -name clamp-config -print`;
$CLAMP_CONFIG =~ s/^\s+//;
$CLAMP_CONFIG =~ s/\s+$//;
my $CLAMP_CXXFLAGS=`$CLAMP_CONFIG --build --cxxflags`;
$CLAMP_CXXFLAGS =~ s/^\s+//;
$CLAMP_CXXFLAGS =~ s/\s+$//;
my $CLAMP_LDFLAGS=`$CLAMP_CONFIG --build --ldflags`;
$CLAMP_LDFLAGS =~ s/^\s+//;
$CLAMP_LDFLAGS =~ s/\s+$//;
my $SHARED_CXXFLAGS="$CLAMP_CXXFLAGS -I$AMPTESTINC -I/usr/include -I$CLANG_AMP_BUILD_DIR/compiler/lib/clang/3.5.0/include/";

### Execute tests
use constant PASS => 0;
use constant SKIP => 1;
use constant FAIL => 2;

log_message("Test: $test");

# Read test configuration
undef %Test::config;

my $conf_file = try_find_file_by_extension(abs_path(dirname(($test))), $tests_root, "conf");

if(-e $conf_file)
{
    my $safe = new Safe('Test');
    $safe->rdo($conf_file) or &exit_message(1, "Cannot open $conf_file");
}

if(not defined $Test::config{'definitions'})
{
    $Test::config{'definitions'} = [{}];
}

# Find "expects error" directives in cpp
open(TEST_CPP, $test) or &exit_message(1, "Cannot open $test");
$Test::config{'expected_success'} = (grep m@//#\s*Expects\s*(\d*)\s*:\s*(warning|error)@i, <TEST_CPP>) == 0;
close(TEST_CPP);

log_message('Compile only: '.bool_str($Test::config{'compile_only'})."\n"
    .'Expected success: '.bool_str($Test::config{'expected_success'}));

# check to see if test has its own main
# This solution taken from https://github.com/pathscale/amp-testsuite/commit/1f9f186d27446e52bd50dbcf429844f3fa308303
my $include_main='';
if (! system ("cat $test | grep ' main *(' > /dev/null")) {
    $include_main="-include $RUNTESTSDIR/test_main.h";
}

# For each set of definitions
chdir($tmpdir);
foreach my $def_set (@{$Test::config{'definitions'}})
{
    # Build and execute test
    my $cflags_defs = '';
    while(my ($k, $v) = each(%{$def_set}))
    {
        $cflags_defs = $cflags_defs.sprintf($cflag_define.' ', $k, $v);
    }
    my $command;
    if ($Test::config{'compile_only'}) {
        $command = "\\
            $CLANG_AMP -cc1 -fcxx-exceptions -fsyntax-only -D__CPU__=1 $SHARED_CXXFLAGS $include_main $test $cflags_defs";
    } elsif ($ENV{TEST_CPU} eq "ON") {
        $command = "\\
            $CLANG_AMP -std=c++amp -cpu $SHARED_CXXFLAGS $include_main $test $AMPTESTLIB $cflags_defs $CLAMP_LDFLAGS -o $tmpdir/test.out -g";
    } else {
            $command = "\\
                $CLANG_AMP $SHARED_CXXFLAGS $include_main $test $AMPTESTLIB $cflags_defs $CLAMP_LDFLAGS -o $tmpdir/test.out -g";
    }

    log_message("Command: $command\n"
        ."Build output:\n"
        ."<<<");
    my $build_exit_code = system($command);
    my $build_exit_signal = $build_exit_code & 127;
    log_message(">>>\n"
        ."Build exit code: $build_exit_code");
    $build_exit_code >>= 8;

    my $exec_exit_code = 0;
    my $exec_exit_signal = 0;
    if((not $Test::config{'compile_only'}) && $build_exit_code == 0 && $Test::config{'expected_success'})
    {
        log_message("Execution output:\n"
            .'<<<');
        $exec_exit_code = system("$test_exec");
        $exec_exit_signal = $exec_exit_code & 127;
        log_message(">>>\n"
            ."Execution exit code: $exec_exit_code");
        $exec_exit_code >>= 8;
    }

    # Interpret result
    my $result;

    if ($build_exit_signal || $exec_exit_signal)
    {
        $result = FAIL;
    }
    elsif(not $Test::config{'expected_success'}) # Negative test
    {
        if($build_exit_code != 0)
        {
            $result = PASS;
        }
        else
        {
            $result = FAIL;
        }
    }
    elsif($Test::config{'compile_only'}) # Compile only test
    {
        if($build_exit_code == 0)
        {
            $result = PASS;
        }
        else
        {
            $result = FAIL;
        }
    }
    else # Executable test
    {
        if($build_exit_code != 0)
        {
            $result = FAIL;
        }
        elsif($exec_exit_code == 0)
        {
            $result = PASS;
        }
        elsif($exec_exit_code == 2)
        {
            $result = SKIP;
        }
        else
        {
            $result = FAIL;
        }
    }

    if($result == PASS)
    {
        log_message('Result: passed');
    }
    elsif($result == FAIL)
    {
        log_message('Result: failed');
    }
    elsif($result == SKIP)
    {
        log_message('Result: skipped');
    }
    else
    {
        exit_message(1, "Unexpected result!");
    }
}
log_message("=====================================================");

### Subroutines
# Use: exit_message(code, msg)
sub exit_message
{
    if(@_ != 2) { die('exit_message expects 2 arguments'); }
    print("\n".($_[0] == 0 ? 'SUCCESS' : 'FAILURE').": ".$_[1]);
    exit($_[0]);
}

# Use: log_message(msg, ...)
sub log_message
{
    print "@_\n";
}

# Use: bool_str(val)
# Returns: string 'true'/'false'
sub bool_str
{
    return $_[0] ? 'true' : 'false';
}

## Use: get_files_by_extension($start_dir, $ext);
## Returns: List of files with given extension
sub get_files_by_extension($$)
{
    my $dir = $_[0];
    my $ext = $_[1];

	my @files = `ls $dir`;
    my @ext_files;
	
    for my $file (@files)
    {
		if($file =~ /\.$ext$/i)
		{
			chomp($file);
			push(@ext_files, $file);
		}
    }

    return @ext_files;    	
}

## Use: try_find_file_by_extension($start_dir, $end_dir, $ext);
## Returns: Relative path to file found. Empty if no file exists. -1 if error is encountered.
sub try_find_file_by_extension($$$)
{
	my $start_dir = $_[0];
	my $end_dir = $_[1];
	my $ext = $_[2];
	
	if(index($start_dir, $end_dir) == -1)
	{
		print "ERROR: $start_dir is not a subdirectory of $end_dir.";
		return -1;
	}
	
	my @files;

	do
	{	
		@files = get_files_by_extension($start_dir, $ext);
		
		if(@files > 1)
		{
			print "Error: More than one (*.$ext) files present in directory $start_dir\n";
			return -1;
		}
		
		if(@files != 0)
		{
			my $file = $files[0];
			
			if(-e "$start_dir/$file")
			{
				return "$start_dir/$file";
			}
		}
		
		# Move to parent directory to continue search
		$start_dir = dirname($start_dir);
	}
	while(index($start_dir, $end_dir) != -1);
	
	return "";
}
